package com.gitee.wsl.mathematics.vector.vec3

import com.gitee.wsl.ext.array.DoubleArray
import com.gitee.wsl.math.random.randomDouble
import com.gitee.wsl.io.dataproxy.DataGet
import com.gitee.wsl.io.dataproxy.asProxy
import com.gitee.wsl.mathematics.coordinate.d3.Coordinate3D
import com.gitee.wsl.mathematics.level.matrix.CoordinateMatrix3D
import com.gitee.wsl.mathematics.vector.ext.dot
import com.gitee.wsl.mathematics.vector.vec4.Vec4
import com.gitee.wsl.mathematics.vector.vec2.Vec2


open class Vec3(override val proxy: DataGet<Double>):
    Coordinate3D<Vec3> {
    constructor(x: Double=0.0,
                y: Double=0.0,
                z: Double=0.0):this(DoubleArray(x,y,z).asProxy())
    constructor(a: Number) : this(a.toDouble(), a.toDouble(), a.toDouble())
    constructor(x: Number, y: Number, z: Number) : this(x.toDouble(), y.toDouble(), z.toDouble())
    constructor(base: Vector3<*,*>) : this(base.x, base.y, base.z)

    override val self
        get() = this

    //override val xy get() = Vec2(x,y)
    //override val xz get() = Vec2(x,z)
    //override val yz get() = Vec2(y,z)

    //override val vec2 get() = Vec2(x, y)
    //override val vec3 get() = this
    override val vec4 get() = Vec4(x, y, z, 0.0)

    override fun createVec2(a: Double, b: Double): Vec2 = Vec2(x,y)

    override fun create(x: Double, y: Double, z: Double): Vec3 = Vec3(x, y, z)

    companion object {
        val ONE get() = Vec3(1.0, 1.0, 1.0)
        val ZERO get() = Vec3(0.0, 0.0, 0.0)
        val right get() = Vec3(1.0, 0.0, 0.0)
        val left get() = Vec3(-1.0, 0.0, 0.0)
        val up get() = Vec3(0.0, 1.0, 0.0)
        val down get() = Vec3(0.0, -1.0, 0.0)
        val forward get() = Vec3(0.0, 0.0, 1.0)
        val backward get() = Vec3(0.0, 0.0, -1.0)
        val negativeInfinity get() = Vec3(Double.NEGATIVE_INFINITY)
        val positiveInfinity get() = Vec3(Double.POSITIVE_INFINITY)

        val UNIT_X = Vec3(1f, 0f, 0f)
        val UNIT_Y = Vec3(0f, 1f, 0f)
        val UNIT_Z = Vec3(0f, 0f, 1f)

        val X_AXIS = Vec3(1f, 0f, 0f)
        val Y_AXIS = Vec3(0f, 1f, 0f)
        val Z_AXIS = Vec3(0f, 0f, 1f)

        val NEG_X_AXIS = Vec3(-1f, 0f, 0f)
        val NEG_Y_AXIS = Vec3(0f, -1f, 0f)
        val NEG_Z_AXIS = Vec3(0f, 0f, -1f)

        val UNIT_XYZ = ONE.normalized
    }

    override fun toString(): String {
        val xInt = x.toInt(); val yInt = y.toInt(); val zInt = z.toInt()
        val xs = if (x == xInt.toDouble()) xInt.toString() else x.toString()
        val ys = if (y == yInt.toDouble()) yInt.toString() else y.toString()
        val zs = if (z == zInt.toDouble()) zInt.toString() else z.toString()

        return "[$xs, $ys, $zs]"
    }

    override fun equals(other: Any?) = other is Vec3 && x == other.x && y == other.y && z == other.z
    override fun hashCode() = 31 * (31 * x.hashCode() + y.hashCode()) + z.hashCode()

}

fun Vec3.toMutableVec3() = MutableVec3(x, y, z)

fun Vec3.Companion.random() = Vec3(randomDouble(), randomDouble(), randomDouble())

fun Vec3.Companion.random(min: Double, max: Double)=  Vec3(randomDouble(min, max), randomDouble(min, max), randomDouble(min, max))


internal fun Vec3.Companion.read3DPoints(input: String): Set<Vec3> {
    val points = mutableSetOf<Vec3>()
    val inputs = input.split("\\*".toRegex()).dropLastWhile { it.isEmpty() }

    for (s0 in inputs) {
        val s = s0.trim()
        if (s.isEmpty()) continue

        if (s.startsWith("(") && s.endsWith("]"))
            points.addAll(CoordinateMatrix3D.fromString(s).coordinates)
        else
            points.add(Vec3.fromString(s))
    }

    return points
}

/**
 * Parses a 3D Coordinate from a string.
 * @param point String
 * @return 3D Coordinate
 */
fun Vec3.Companion.fromString(point: String): Vec3 {
    val point0 = point.trim { it <= ' ' }.replace("[\\[\\]\\s]".toRegex(), "")
    val parts = point0.split(",".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
    if (parts.size != 3) throw IllegalArgumentException("Invalid Coordinate Length")

    val x = parts[0].toDouble(); val y = parts[1].toDouble(); val z = parts[2].toDouble()
    return Vec3(x, y, z)
}

fun Vec3.nearZero(): Boolean {
    val s = 1e-8
    return abs(x) < s && abs(y) < s && abs(z) < s
}


fun Vec3.Companion.randomInUnitSphere(): Vec3 {
    while (true) {
        val p = Vec3.random(-1.0, 1.0)
        if (p.squaredLength < 1.0) {
            return p
        }
    }
}


fun Vec3.Companion.randomUnitVector(): Vec3 {
    return randomInUnitSphere().unit
}

fun Vec3.Companion.randomOnHemisphere(normal: Vec3): Vec3 {
    val onUnitSphere = randomUnitVector()
    return if (onUnitSphere.dot(normal) > 0.0) { // In the same hemisphere as the normal
        onUnitSphere
    } else {
        -onUnitSphere
    }
}

fun Vec3.Companion.randomInUnitDisk(): Vec3 {
    while(true) {
        val p = Vec3(randomDouble(-1.0, 1.0), randomDouble(-1.0, 1.0), 0.0)
        if (p.squaredLength < 1.0) {
            return p
        }
    }
}